/* simulated_annealing.h -- This belongs to gneural_network

   gneural_network is the GNU package which implements a programmable neural network.

   Copyright (C) 2016 Jean Michel Sellier
   <jeanmichel.sellier@gmail.com>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

// train the neural network by means of simulated annealing to optimize the weights
/*
 * output: 是否输出到屏幕
 * mmax :外层循环数最大值
 * nmax : 内层循环数最大值
 * kbtmin,kbtmax 随机温度值范围
 * eps: 精确度
 * */
void simulated_annealing(int output /* screen output - on/off */,
    int mmax /* outer loop - number of effective temperature steps*/,
    int nmax /* inner loop - number of test configurations */,
    double kbtmin /* effective temperature minimum */,
    double kbtmax /* effective temperature maximum */,
    double eps /* numerical accuracy */){
  register int m,n;
  int i,j,k;
  double err;
  double e0;
  double de;
  double kbt;
  double e_best;
  double *wbackup;
  double *wbest;

  //权重备份空间
  wbackup=malloc((NNUM*MAX_IN+1)*sizeof(*wbackup));
  if(wbackup==NULL){
    printf("RND: Not enough memory to allocate\ndouble *wbackup\n");
    exit(0);
  }

  //存储最优权重值
  wbest=malloc((NNUM*MAX_IN+1)*sizeof(*wbest));
  if(wbest==NULL){
    printf("SA: Not enough memory to allocate\ndouble *wbest\n");
    exit(0);
  }


  //初始误差值
  e_best=err=e0=1.e8; // just a big number

  //外层循环开始
  for(m=0;(m<mmax) && (e0>eps);m++){

    //一个因子，从kbtmax-kbtmin 每次递减一个固定值参与到内层循环中
    kbt=kbtmax-m*(kbtmax-kbtmin)/(mmax-1);

    for(n=0;(n<nmax) && (e0>eps);n++){
      // backup the old weights
      k=0;
      //备份权重值
      for(i=0;i<NNUM;i++){
        for(j=0;j<NEURON[i].nw;j++){
          wbackup[k]=NEURON[i].w[j];
          k++;
        }
      }
      // new random configuration
      // 产生一组新的权重值
      randomize();
      // compute the error
      // 计算新权重值的误差量
      err=error(L2);
      // update energy landscape
      // e0为上次误差值，这里计算de为误差微分
      de=err-e0;
      // decides what configuration to keep
      if(de>0.){
        // if(de<=0.) just accept the new configuration
        // otherwise treat it probabilistically
        // 如果微分值大于0 ，代表误差增大，则试图改变策略
        double p;
        //计算一个稳定阈值，随着循环的增加稳定因素会越小，也即下面这个if的通过率会随着外层循环的增加而越不容易进入
        p=exp(-e0/kbt);
        if(rnd()<p){
          // accept the new configuration
          e0=err;
        } else {
          // reject the new configuration
          //wbackup 为权重的备份数据，这里是取消新策略，将权重值恢复，意思为本次随机产生的权重值不适合本策略
          k=0;
          for(i=0;i<NNUM;i++){
            for(j=0;j<NEURON[i].nw;j++){
              NEURON[i].w[j]=wbackup[k];
              k++;
            }
          }
        }
      } else {
        // accept the new configuration
        // 这里代表微分为负数，误差减小，不做任何处理
        e0=err;
      }
      //存储最佳神经网络权重值
      //如果本次误差比历史最优误差小，则将本次权重存入wbest中
      if(e_best>e0){
        k=0;
        for(i=0;i<NNUM;i++){
          for(j=0;j<NEURON[i].nw;j++){
            wbest[k]=NEURON[i].w[j];
            k++;
          }
        }
        e_best=e0;
      }
    }
    if(output==ON) printf("SA: %d %g %g\n",m,kbt,e_best);
  }
  // keep the best solution found
  // 将最佳权重赋值给神经网络
  k=0;
  for(i=0;i<NNUM;i++){
    for(j=0;j<NEURON[i].nw;j++){
      NEURON[i].w[j]=wbest[k];
      k++;
    }
  }
  if(output==ON) printf("\n");
  //清理
  free(wbackup);
  free(wbest);
}
